// Copyright (c) 2023 刻BITTER
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.


#pragma once


#ifdef ARDUINO
    #include <Arduino.h>
#endif


#include <stdint.h>

#include "oled_basic_font.h"
#include "oled_basic_low_level_common.h"

//#define _OLED_BASIC_DECIMAL_NUM_ONLY


namespace oled_basic {

    using _NumType = int32_t;
    using _UnsignedNumType = uint32_t;
    constexpr size_t _num_length_with_10_base = 10;


    enum class num_base : uint8_t {
        n10 = 10,
        n16 = 16,

        dec = 10,
        hex = 16
    };


    template <typename OledLowLevel>
    class OledDriver {
       public:
        static void enable_inverse_display() {
            OledLowLevel::write_cmd(0xA7);  // 反色显示
        }


        static void disable_inverse_display() {
            OledLowLevel::write_cmd(0xA6);  // 反色显示
        }


        static void enable_screen_flip() {
            OledLowLevel::write_cmd(0xC8);  // 反转显示
            OledLowLevel::write_cmd(0xA0);
        }


        static void disable_screen_flip() {
            OledLowLevel::write_cmd(0xC0);  // 正常显示
            OledLowLevel::write_cmd(0xA1);
        }


        /**
         * @brief 设置坐标
         *
         * page addressing 模式，列地址超过127 溢出后自动返回0
         *
         * @param x 列地址
         * @param y 页地址
         */
        static void set_position(uint8_t column, uint8_t page) {
            OledLowLevel::write_cmd(0xb0 + page);
            OledLowLevel::write_cmd(((column & 0xf0) >> 4) | 0x10);
            OledLowLevel::write_cmd(column & 0x0f);
        }


        // 开启OLED显示
        static void display_on(void) {
            OledLowLevel::write_cmd(0X8D);  // SET DCDC命令
            OledLowLevel::write_cmd(0X14);  // DCDC ON
            OledLowLevel::write_cmd(0XAF);  // DISPLAY ON
        }


        // 关闭OLED显示
        static void display_off(void) {
            OledLowLevel::write_cmd(0X8D);  // SET DCDC命令
            OledLowLevel::write_cmd(0X10);  // DCDC OFF
            OledLowLevel::write_cmd(0XAE);  // DISPLAY OFF
        }


        static uint8_t fill_area(uint8_t data, uint8_t start_col, uint8_t start_page, uint8_t col_count, uint8_t page_count) {
            while (page_count--) {
                set_position(start_col, start_page);

                if constexpr (OledLowLevel::manual_start_end_for_data_transfer) {
                    OledLowLevel::start_transmission();
                }

                for (uint8_t n = 0; n < col_count; n++) {
                    OledLowLevel::write_byte(data, write_mode::non_inverse);
                }

                if constexpr (OledLowLevel::manual_start_end_for_data_transfer) {
                    OledLowLevel::end_transmission();
                }

                ++start_page;
            }

            return start_col + col_count;
        }


        /**
         * @brief 用0 填满显存
         *
         */
        static void clear_screen() {
            fill_area(0, 0, 0, OledLowLevel::column_count, OledLowLevel::page_count);
        }


        static uint8_t fill_trail(uint8_t data, uint8_t start_col, uint8_t start_page, uint8_t current_col, const FontMeta &font, uint8_t max_count) {
            return fill_area(data, current_col, start_page, start_col + font.width * max_count - current_col, font.height);
        }


        static uint8_t fill_trail(uint8_t data, uint8_t start_col, uint8_t start_page, uint8_t current_col, uint8_t height, uint8_t max_width) {
            return fill_area(data, current_col, start_page, start_col + max_width - current_col, height);
        }


        /**
         * @brief 文本或数字更新后，清除右侧残留的无效数据。
         *
         * 比如：显示了较大数字后，再显示小数字，必须清空后面空出来的位置。
         * 如果先显示-100，然后变成-99，不做清空的话，实际显示出来就是-990。
         * 整体清屏再重写会产生一个空白帧，导致闪烁，所以只清空后面多出来的部分。
         *
         * @param start_col    文字显示区域的起始列坐标
         * @param start_page   起始页坐标
         * @param current_col  输出更新的文字后，put_str 或put_signed 函数返回的当前列坐标
         * @param font         显示区域使用的字体
         * @param max_count    预定区域内最大文字宽度，如果一个区域最多显示三位数，则最大宽度就是三
         * @return uint8_t     输出数据后的当前列坐标
         */
        static uint8_t clear_trail(uint8_t start_col, uint8_t start_page, uint8_t current_col, const FontMeta &font, uint8_t max_count) {
            return fill_trail(0, start_col, start_page, current_col, font, max_count);
        }


        static uint8_t clear_trail(uint8_t start_col, uint8_t start_page, uint8_t current_col, uint8_t height, uint8_t max_width) {
            return fill_trail(0, start_col, start_page, current_col, height, max_width);
        }

        
        static uint8_t put_char(uint8_t ch, const FontMeta &font, uint8_t col, uint8_t page, write_mode m = write_mode::non_inverse) {
            ch -= font.offset;

            uint8_t font_width = font.width;
            uint8_t font_height = font.height;
            const uint8_t *data_ptr = font.font_data + (font_width * font_height * ch);


            while (font_height) {
                set_position(col, page);

                data_ptr = OledLowLevel::write_data(data_ptr, font_width, m);

                ++page;
                --font_height;
            }

            return col + font_width;
        }

        
        static uint8_t put_str(const char *str, const FontMeta &font, uint8_t col, uint8_t page, write_mode m = write_mode::non_inverse) {
            while (*str) {
                uint8_t ch = *str;
                col = put_char(ch, font, col, page, m);
                // col += font.width;
                ++str;
            }

            return col;
        }

#ifdef _OLED_BASIC_DECIMAL_NUM_ONLY
        static uint8_t put_unsigned(_UnsignedNumType num, const FontMeta &font, uint8_t col, uint8_t page, write_mode m = write_mode::non_inverse){
#else
        static uint8_t put_unsigned(_NumType num, const FontMeta &font, uint8_t col, uint8_t page, write_mode m = write_mode::non_inverse, num_base base = num_base::n10) {
#endif  // FROM: https://github.com/MCUdude/MiniCore/blob/master/avr/cores/MCUdude_corefiles/Print.cpp
        // LICENSE: LGPL V2.1
            char buf[_num_length_with_10_base + 1];  // 无符号32 位数最大10 位4294967295，加1 字节，用来存放\0

        char *str = &buf[sizeof(buf) - 1];

        *str = '\0';
#ifdef _OLED_BASIC_DECIMAL_NUM_ONLY
        uint8_t b = 10;
#else
            uint8_t b = static_cast<uint8_t>(base);
#endif
        do {
            char c = num % b;
            num /= b;

            *(--str) = (c < 10) ? (c + '0') : (c + 'A' - 10);
        } while (num);

        return put_str(str, font, col, page, m);
    }


    static uint8_t
    put_signed(_NumType num, const FontMeta &font, uint8_t col, uint8_t page, write_mode m = write_mode::non_inverse) {
        if (num < 0) {
            // col = put_str("-", font, col, page, m);
            col = put_char('-', font, col, page, m);
            num = -num;
        }

        return put_unsigned(num, font, col, page, m);
    }


    static void run_init_sequence(void) {
        for (uint8_t i = 0; i < static_cast<uint8_t>(OledLowLevel::init_sequence_length); ++i) {
#ifdef ARDUINO
            uint8_t cmd = pgm_read_byte(&(OledLowLevel::init_sequence[i]));
            OledLowLevel::write_cmd(cmd);
#else
                OledLowLevel::write_cmd(OledLowLevel::init_sequence[i]);
#endif
        }

        clear_screen();
    }
};  // namespace oled_basic


}  // namespace oled_basic